#include <assimp/postprocess.h>
#include <assimp/scene.h>

#include <assimp/Importer.hpp>
#include <iostream>
#include <string>

using namespace std;

bool importFile(const std::string& pFile);
void processScene(const aiScene* scene);
void processMeshNormals(aiMesh* mesh);
void processMeshVertices(aiMesh* mesh);
void processMesh(aiMesh* mesh);
void processMeshFaces(aiMesh* mesh);
string getFaceType(const aiFace* face);
ostream& print_indent(int level);

ostream& print_indent(int level) {
    for (auto i{0}; i < level; i++) {
        cout << "\t";
    }
    return cout;
}

bool importFile(const std::string& pFile) {
    cout << "import " << pFile << endl;
    // Create an instance of the Importer class
    Assimp::Importer importer;

    // And have it read the given file with some example postprocessing
    // Usually - if speed is not the most important for you - you'll
    // probably to request more postprocessing than we do in this example.
    const aiScene* scene = importer.ReadFile(
        pFile, aiProcess_CalcTangentSpace | aiProcess_Triangulate |
                   aiProcess_JoinIdenticalVertices | aiProcess_SortByPType);

    // if the import failed, report it
    if (!scene) {
        cout << "no scene! error: " << importer.GetErrorString() << endl;
        return false;
    }

    // Now we can access the file's contents.
    processScene(scene);

    // We're done. Everything will be cleaned up by the importer destructor
    return true;
}

void processScene(const aiScene* scene) {
    if (scene == nullptr) {
        cerr << "scene is null" << endl;
        return;
    }
    if (!scene->HasMeshes()) {
        cout << "There are no meshes!" << endl;
    }

    unsigned int meshesCount = scene->mNumMeshes;
    cout << "There are " << meshesCount << " meshes" << endl;
    aiMesh** meshes = scene->mMeshes;
    for (int i = 0; i < meshesCount; i++) {
        aiMesh* mesh = meshes[i];
        processMesh(mesh);
    }
}

void processMesh(aiMesh* mesh) {
    cout << "name: " << mesh->mName.C_Str() << endl;
    processMeshVertices(mesh);
    processMeshNormals(mesh);
    processMeshFaces(mesh);
}

string getFaceType(const aiFace* face) {
    if (face == nullptr) {
        return "";
    }
    string faceType;
    switch (face->mNumIndices) {
        case 1:
            faceType = "POINTS";
            break;
        case 2:
            faceType = "LINES";
            break;
        case 3:
            faceType = "TRIANGLES";
            break;
        default:
            faceType = "POLYGON";
            break;
    }

    return faceType;
}

void processMeshFaces(aiMesh* mesh) {
    if (mesh == nullptr) {
        return;
    }
    unsigned int facesCount = mesh->mNumFaces;
    print_indent(1) << "There are " << facesCount << " faces in the mesh "
                    << endl;
    for (int i = 0; i < facesCount; i++) {
        const struct aiFace* face = &mesh->mFaces[i];
        unsigned int indexCount = face->mNumIndices;
        print_indent(2) << "face " << i << " [" << getFaceType(face) << "] has "
                        << indexCount << " indices : ";
        unsigned int* indices = face->mIndices;
        for (int m = 0; m < indexCount; m++) {
            cout << indices[m] << " ";
        }
        cout << endl;
    }
}

void processMeshVertices(aiMesh* mesh) {
    unsigned int vertexCount = mesh->mNumVertices;
    aiVector3D* vertices = mesh->mVertices;
    print_indent(1) << "The vertices are:\n";
    for (int i = 0; i < vertexCount; i++) {
        print_indent(2) << i << " : " << vertices[i].x << ", " << vertices[i].y
                        << ", " << vertices[i].z << endl;
    }
}

void processMeshNormals(aiMesh* mesh) {
    if (!mesh->HasNormals()) {
        cout << "mesh " << mesh->mName.C_Str() << " has no normals!" << endl;
        return;
    }

    unsigned int normalCount = mesh->mNumVertices;
    aiVector3D* normals = mesh->mNormals;
    print_indent(1) << "The normals are:\n";
    for (int i = 0; i < normalCount; i++) {
        print_indent(2) << i << " : " << normals[i].x << ", " << normals[i].y
                        << ", " << normals[i].z << endl;
    }
}

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << " the file is missing" << endl;
        return -1;
    }
    importFile(argv[1]);
}
